package com.magnificent.panda.model.generator.log;

import com.magnificent.panda.model.entity.FlowActor;
import com.magnificent.panda.model.entity.FlowActorType;
import com.magnificent.panda.model.entity.FlowEvent;
import com.magnificent.panda.model.generator.Parser;
import com.magnificent.panda.model.generator.log.taxi.OrderAction;
import com.magnificent.panda.model.generator.log.taxi.TaxiConstants;
import com.magnificent.panda.model.generator.log.taxi.dispatcher.DispatcherOrderAction;
import com.magnificent.panda.model.generator.log.taxi.dispatcher.MakeOrder;
import com.magnificent.panda.model.generator.log.taxi.driver.AcceptOrder;
import com.magnificent.panda.model.generator.log.taxi.driver.DriverOrderAction;
import com.magnificent.panda.model.generator.log.taxi.driver.RefuseOrder;
import com.silverhaze.commons.utils.TimeConstants;
import com.silverhaze.commons.utils.collections.list.MetaArrayList;
import com.silverhaze.commons.utils.collections.list.MetaList;
import com.silverhaze.commons.utils.other.Pair;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.text.ParseException;
import java.util.*;

/**
 * User: Babanin
 * Date: 13.03.2010
 * Time: 1:28:42
 */
public class TaxiLogParser extends Parser {
    private static final String IS = "is ";
    private static final String INFO = "  INFO: ";

    private static final String ORDER = "Заказ ";
    private static final String ACCEPT = " принят";
    private static final String REFUSE = " отклонен";

    private static final String MAKE_ORDER = "Order number for phoneNumber '";

    @Override
    public MetaList<Pair<FlowActor, FlowActor>> parseSeparately(String filename) {
        MetaList<OrderAction> orderActions = new MetaArrayList<OrderAction>();

        try {
            orderActions.addAll(parseLogFile(filename));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }

        if (orderActions.size() == 0) {
            return new MetaArrayList<Pair<FlowActor, FlowActor>>();
        }

        Set<Integer> driverIds = new HashSet<Integer>();
        for (OrderAction orderAction : orderActions) {
            if (orderAction instanceof DispatcherOrderAction) {
                driverIds.add(((DispatcherOrderAction) orderAction).getDriverId());
            }
        }

        long startTime = orderActions.get(0).getDate().getTime();

        MetaList<Pair<FlowActor, FlowActor>> result = new MetaArrayList<Pair<FlowActor, FlowActor>>();
        for (Integer driverId : driverIds) {
            FlowActor dispatcher = new FlowActor("Диспетчер");
            dispatcher.setActorType(FlowActorType.MASTER);

            FlowActor driver = new FlowActor("Водитель №" + driverId);
            driver.setActorType(FlowActorType.SLAVE);

            for (DriverOrderAction driverOrderAction : getDriverOrderActions(orderActions, driverId)) {
                driver.getEvents().add(new FlowEvent(driverOrderAction.getOrderId(), (driverOrderAction.getDate().getTime() - startTime) / TimeConstants.MLS_IN_MIN,
                        driverOrderAction.toString()));
            }

            for (DispatcherOrderAction dispatcherOrderAction : getDispatcherOrderActions(orderActions, driverId)) {
                int orderId = dispatcherOrderAction instanceof MakeOrder ? ((MakeOrder) dispatcherOrderAction).getOrderId() : -1;
                dispatcher.getEvents().add(new FlowEvent(orderId, (dispatcherOrderAction.getDate().getTime() - startTime) / TimeConstants.MLS_IN_MIN,
                        dispatcherOrderAction.toString()));
            }

            dispatcher.setPartner(driver);
            driver.setPartner(dispatcher);

            result.add(new Pair<FlowActor, FlowActor>(dispatcher, driver));
        }


        FlowActor dispatcher = new FlowActor("Диспетчер");
        FlowActor driver = new FlowActor("Все водители");

        for (OrderAction orderAction : orderActions) {
            if (orderAction instanceof DriverOrderAction) {
                int orderId = ((DriverOrderAction) orderAction).getOrderId();
                driver.getEvents().add(new FlowEvent(orderId, (orderAction.getDate().getTime() - startTime) / TimeConstants.MLS_IN_MIN,
                        orderAction.toString()));
            }
        }

        for (OrderAction dispatcherOrderAction : orderActions) {
            if (dispatcherOrderAction instanceof DispatcherOrderAction) {
                int orderId = dispatcherOrderAction instanceof MakeOrder ? ((MakeOrder) dispatcherOrderAction).getOrderId() : -1;
                dispatcher.getEvents().add(new FlowEvent(orderId, (dispatcherOrderAction.getDate().getTime() - startTime) / TimeConstants.MLS_IN_MIN,
                        dispatcherOrderAction.toString()));
            }
        }

        dispatcher.setPartner(driver);
        driver.setPartner(dispatcher);

        result.add(new Pair<FlowActor, FlowActor>(dispatcher, driver));

        return result;
    }

    public Pair<FlowActor, List<FlowActor>> parse(String filename) {
        MetaList<OrderAction> orderActions = new MetaArrayList<OrderAction>();

        try {
            orderActions.addAll(parseLogFile(filename));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }

        if (orderActions.size() == 0) {
            return new Pair<FlowActor, List<FlowActor>>();
        }

        Set<Integer> driverIds = new HashSet<Integer>();
        for (OrderAction orderAction : orderActions) {
            if (orderAction instanceof DispatcherOrderAction) {
                driverIds.add(((DispatcherOrderAction) orderAction).getDriverId());
            }
        }

        long startTime = orderActions.get(0).getDate().getTime();

        List<FlowActor> drivers = new MetaArrayList<FlowActor>();
        for (Integer driverId : driverIds) {
            FlowActor driver = new FlowActor("Водитель №" + driverId);
            driver.setActorType(FlowActorType.SLAVE);

            for (DriverOrderAction driverOrderAction : getDriverOrderActions(orderActions, driverId)) {
                driver.getEvents().add(new FlowEvent(driverOrderAction.getOrderId(), (driverOrderAction.getDate().getTime() - startTime) / TimeConstants.MLS_IN_MIN,
                        driverOrderAction.toString()));
            }

            drivers.add(driver);
        }

        FlowActor dispatcherAll = new FlowActor("Диспетчер");
        dispatcherAll.setActorType(FlowActorType.MASTER);
        for (OrderAction dispatcherOrderAction : orderActions) {
            if (dispatcherOrderAction instanceof DispatcherOrderAction) {
                int orderId = dispatcherOrderAction instanceof MakeOrder ? ((MakeOrder) dispatcherOrderAction).getOrderId() : -1;
                dispatcherAll.getEvents().add(new FlowEvent(orderId, (dispatcherOrderAction.getDate().getTime() - startTime) / TimeConstants.MLS_IN_MIN,
                        dispatcherOrderAction.toString()));
            }
        }


        return new Pair<FlowActor, List<FlowActor>>(dispatcherAll, drivers);
    }


    public static MetaList<OrderAction> parseLogFile(String logFilePath) throws FileNotFoundException, ParseException {
        MetaList<OrderAction> actions = new MetaArrayList<OrderAction>();
        Scanner scanner = new Scanner(new FileInputStream(logFilePath), "UTF-8");

        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();

            int pos = line.indexOf(INFO);
            int firstDigit = getFirstDigitPos(line);

            if (firstDigit == -1 || pos == -1) {
                continue;
            }

            Date date = TaxiConstants.DATE_FORMAT.parse(line.substring(firstDigit, pos));

            line = line.substring(pos + INFO.length());

            if (line.startsWith(MAKE_ORDER)) {
                OrderAction action = parseMakeOrder(line, date);

                if (action != null) {
                    actions.add(action);
                }
            } else if (line.contains(ORDER)) {
                if (line.contains(ACCEPT)) {
                    OrderAction action = parseAcceptOrder(line, date);

                    if (action != null) {
                        actions.add(action);
                    }
                } else if (line.contains(REFUSE)) {
                    OrderAction action = parseRefuseOrder(line, date);

                    if (action != null) {
                        actions.add(action);
                    }
                }
            }
        }

        scanner.close();
        return actions.orderBy(new Comparator<OrderAction>() {
            public int compare(OrderAction o1, OrderAction o2) {
                return o1.getDate().compareTo(o2.getDate());
            }
        });
    }

    private static int getFirstDigitPos(String line) {
        if(line.length() == 0){
            return -1;
        }

        char[] chars = line.toCharArray();

        int i = 0;
        while (!Character.isDigit(chars[i])) {
            i++;

            if (i >= chars.length) {
                return -1;
            }
        }

        return i;
    }

    private static OrderAction parseRefuseOrder(String line, Date date) {
        line = line.replaceAll(ORDER, "").trim();

        try {
            int orderId = Integer.parseInt(line.substring(0, line.indexOf(" ")).trim());
            int driverId = Integer.parseInt(line.substring(line.lastIndexOf(" ") + 2, line.length() - 2).trim());

            RefuseOrder refuseOrder = new RefuseOrder(driverId, orderId);
            refuseOrder.setDate(date);

            return refuseOrder;
        } catch (Exception e) {
            return null;
        }
    }

    private static OrderAction parseAcceptOrder(String line, Date date) {
        line = line.replaceAll(ORDER, "").trim();

        try {
            int orderId = Integer.parseInt(line.substring(0, line.indexOf(" ")).trim());
            int driverId = Integer.parseInt(line.substring(line.lastIndexOf(" ") + 2, line.length() - 2).trim());

            AcceptOrder acceptOrder = new AcceptOrder(driverId, orderId);
            acceptOrder.setDate(date);

            return acceptOrder;
        } catch (Exception e) {
            return null;
        }
    }

    private static OrderAction parseMakeOrder(String line, Date date) {
        line = line.replaceAll(MAKE_ORDER, "");

        int driverId = Integer.parseInt(line.substring(0, line.indexOf("'")).trim());
        int orderId = Integer.parseInt(line.substring(line.lastIndexOf(IS) + IS.length()));

        MakeOrder makeOrder = new MakeOrder(driverId, orderId);
        makeOrder.setDate(date);

        return makeOrder;
    }

    public static MetaList<DriverOrderAction> getDriverOrderActions(MetaList<OrderAction> actionList, int driverId) {
        MetaList<DriverOrderAction> result = new MetaArrayList<DriverOrderAction>();

        for (OrderAction orderAction : actionList) {
            if (orderAction instanceof DriverOrderAction) {
                DriverOrderAction action = (DriverOrderAction) orderAction;

                if (driverId != -1) {
                    if (driverId == action.getDriverId()) {
                        result.add(action);
                    }
                } else {
                    result.add(action);
                }
            }
        }

        return result;
    }

    public static MetaList<DispatcherOrderAction> getDispatcherOrderActions(MetaList<OrderAction> actionList, int driverId) {
        MetaList<DispatcherOrderAction> result = new MetaArrayList<DispatcherOrderAction>();

        for (OrderAction orderAction : actionList) {
            if (orderAction instanceof DispatcherOrderAction) {
                DispatcherOrderAction action = (DispatcherOrderAction) orderAction;

                if (driverId != -1) {
                    if (driverId == action.getDriverId()) {
                        result.add(action);
                    }
                } else {
                    result.add(action);
                }
            }
        }

        return result;
    }
}

  /* converted to utf8 */